/*-------------------------------------------------------------------------
 *
 * fe-protocol3.c
 *	  与前端/后端协议版本 3 相关的特定函数
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/interfaces/libpq/fe-protocol3.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres_fe.h"

#include <ctype.h>
#include <fcntl.h>

#ifdef WIN32
#include "win32.h"
#else
#include <unistd.h>
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#endif
#endif

#include "libpq-fe.h"
#include "libpq-int.h"
#include "mb/pg_wchar.h"
#include "port/pg_bswap.h"

/*
 * 此宏列出可能是“长”的后端消息类型（超过几千字节）。
 */
#define VALID_LONG_MESSAGE_TYPE(id) \
	((id) == 'T' || (id) == 'D' || (id) == 'd' || (id) == 'V' || \
	 (id) == 'E' || (id) == 'N' || (id) == 'A')


static void fc_handleSyncLoss(PGconn *fc_conn, char fc_id, int fc_msgLength);
static int	fc_getRowDescriptions(PGconn *fc_conn, int fc_msgLength);
static int	fc_getParamDescriptions(PGconn *fc_conn, int fc_msgLength);
static int	fc_getAnotherTuple(PGconn *fc_conn, int fc_msgLength);
static int	fc_getParameterStatus(PGconn *fc_conn);
static int	fc_getNotify(PGconn *fc_conn);
static int	fc_getCopyStart(PGconn *fc_conn, ExecStatusType fc_copytype);
static int	fc_getReadyForQuery(PGconn *fc_conn);
static void fc_reportErrorPosition(PQExpBuffer fc_msg, const char *fc_query,
								int fc_loc, int fc_encoding);
static int	fc_build_startup_packet(const PGconn *fc_conn, char *fc_packet,
								 const PQEnvironmentOption *fc_options);


/*
 * parseInput：如适用，从后端解析输入数据
 * 直到输入耗尽或达到停止状态。
 * 请注意，此函数不会尝试从后端读取更多数据。
 */
void pqParseInput3(PGconn *fc_conn)
{
	char		fc_id;
	int			fc_msgLength;
	int			fc_avail;

	/*
	 * 循环以解析缓冲区中可用的连续完整消息。
	 */
	for (;;)
	{
		/*
		 * 尝试读取消息。首先获取类型代码和长度。如果数据不足，则返回。
		 */
		fc_conn->inCursor = fc_conn->inStart;
		if (pqGetc(&fc_id, fc_conn))
			return;
		if (pqGetInt(&fc_msgLength, 4, fc_conn))
			return;

		/*
		 * 尝试在此处验证消息类型/长度。长度小于4肯定是有问题的。只有对于少数
		 * 消息类型，大长度才应被相信。
		 */
		if (fc_msgLength < 4)
		{
			fc_handleSyncLoss(fc_conn, fc_id, fc_msgLength);
			return;
		}
		if (fc_msgLength > 30000 && !VALID_LONG_MESSAGE_TYPE(fc_id))
		{
			fc_handleSyncLoss(fc_conn, fc_id, fc_msgLength);
			return;
		}

		/*
		 * 如果消息主体尚未全部到位，则无法处理。
		 */
		fc_msgLength -= 4;
		fc_avail = fc_conn->inEnd - fc_conn->inCursor;
		if (fc_avail < fc_msgLength)
		{
			/*
			 * 在返回之前，如果需要，扩大输入缓冲区以容纳
			 * 整个消息。这比将其留给
			 * pqReadData要好，因为我们可以避免在消息很大时进行多次 realloc()
			 * 循环；此外，如果我们无法使缓冲区足够大，我们可以实施合理的
			 * 恢复策略。
			 */
			if (pqCheckInBufferSpace(fc_conn->inCursor + (size_t) fc_msgLength,
									 fc_conn))
			{
				/*
				 * XXX 添加一些更好的恢复代码...计划是跳过
				 * 消息，使用其长度，然后报告错误。此时，只需将其视为同步丢失
				 * （这确实可能是！）
				 */
				fc_handleSyncLoss(fc_conn, fc_id, fc_msgLength);
			}
			return;
		}

		/*
		 * NOTIFY 和 NOTICE 消息可以在任何状态下发生；始终立即处理它们。
		 *
		 * 大多数其他消息仅应在 BUSY 状态下处理。
		 * （特别是在 READY 状态下，我们暂停进一步解析，直到
		 * 应用程序收集当前 PGresult。）
		 *
		 * 然而，如果状态是 IDLE，那就麻烦了；我们需要以某种方式处理
		 * 意外消息。
		 *
		 * ParameterStatus（'S'）消息是一个特例：在 IDLE 状态下，我们
		 * 必须处理它们（这种情况可能发生在由于 SIGHUP 从配置文件
		 * 采用了新值时），否则我们将暂停，直到
		 * BUSY 状态。
		 */
		if (fc_id == 'A')
		{
			if (fc_getNotify(fc_conn))
				return;
		}
		else if (fc_id == 'N')
		{
			if (pqGetErrorNotice3(fc_conn, false))
				return;
		}
		else if (fc_conn->asyncStatus != PGASYNC_BUSY)
		{
			/* 如果不是 IDLE 状态，只需等待... */
			if (fc_conn->asyncStatus != PGASYNC_IDLE)
				return;

			/*
			 * 在 IDLE 状态中出现意外消息；需要以某种方式恢复。
			 * ERROR 消息使用通知处理器处理；
			 * ParameterStatus 正常处理；其他任何消息都只是在显示适当的警告
			 * 通知后被丢弃。（ERROR 很可能是后端告诉我们它为什么
			 * 即将关闭连接，因此我们不想仅仅丢弃它...）
			 */
			if (fc_id == 'E')
			{
				if (pqGetErrorNotice3(fc_conn, false /* 视为通知 */ ))
					return;
			}
			else if (fc_id == 'S')
			{
				if (fc_getParameterStatus(fc_conn))
					return;
			}
			else
			{
				/* 任何其他情况都是意外的，我们简要跳过它 */
				pqInternalNotice(&fc_conn->noticeHooks,
								 "message type 0x%02x arrived from server while idle",
								 fc_id);
				/* 丢弃意外消息 */
				fc_conn->inCursor += fc_msgLength;
			}
		}
		else
		{
			/*
			 * 在 BUSY 状态下，我们可以处理所有内容。
			 */
			switch (fc_id)
			{
				case 'C':		/* 命令完成 */
					if (pqGets(&fc_conn->workBuffer, fc_conn))
						return;
					if (!pgHavePendingResult(fc_conn))
					{
						fc_conn->result = PQmakeEmptyPGresult(fc_conn,
														   PGRES_COMMAND_OK);
						if (!fc_conn->result)
						{
							appendPQExpBufferStr(&fc_conn->errorMessage,
												 libpq_gettext("out of memory"));
							pqSaveErrorResult(fc_conn);
						}
					}
					if (fc_conn->result)
						strlcpy(fc_conn->result->cmdStatus, fc_conn->workBuffer.data,
								CMDSTATUS_LEN);
					fc_conn->asyncStatus = PGASYNC_READY;
					break;
				case 'E':		/* 错误返回 */
					if (pqGetErrorNotice3(fc_conn, true))
						return;
					fc_conn->asyncStatus = PGASYNC_READY;
					break;
				case 'Z':		/* 同步响应，后端准备好进行新的
								 * 查询 */
					if (fc_getReadyForQuery(fc_conn))
						return;
					if (fc_conn->pipelineStatus != PQ_PIPELINE_OFF)
					{
						fc_conn->result = PQmakeEmptyPGresult(fc_conn,
														   PGRES_PIPELINE_SYNC);
						if (!fc_conn->result)
						{
							appendPQExpBufferStr(&fc_conn->errorMessage,
												 libpq_gettext("out of memory"));
							pqSaveErrorResult(fc_conn);
						}
						else
						{
							fc_conn->pipelineStatus = PQ_PIPELINE_ON;
							fc_conn->asyncStatus = PGASYNC_READY;
						}
					}
					else
					{
						/* 提升命令队列并将我们设置为 idle */
						pqCommandQueueAdvance(fc_conn, true, false);
						fc_conn->asyncStatus = PGASYNC_IDLE;
					}
					break;
				case 'I':		/* 空查询 */
					if (!pgHavePendingResult(fc_conn))
					{
						fc_conn->result = PQmakeEmptyPGresult(fc_conn,
														   PGRES_EMPTY_QUERY);
						if (!fc_conn->result)
						{
							appendPQExpBufferStr(&fc_conn->errorMessage,
												 libpq_gettext("out of memory"));
							pqSaveErrorResult(fc_conn);
						}
					}
					fc_conn->asyncStatus = PGASYNC_READY;
					break;
				case '1':		/* 解析完成 */
					/* 如果我们正在进行 PQprepare，那么我们完成了；否则忽略 */
					if (fc_conn->cmd_queue_head &&
						fc_conn->cmd_queue_head->queryclass == PGQUERY_PREPARE)
					{
						if (!pgHavePendingResult(fc_conn))
						{
							fc_conn->result = PQmakeEmptyPGresult(fc_conn,
															   PGRES_COMMAND_OK);
							if (!fc_conn->result)
							{
								appendPQExpBufferStr(&fc_conn->errorMessage,
													 libpq_gettext("out of memory"));
								pqSaveErrorResult(fc_conn);
							}
						}
						fc_conn->asyncStatus = PGASYNC_READY;
					}
					break;
				case '2':		/* 绑定完成 */
				case '3':		/* 关闭完成 */
					/* 对于这些消息类型没有什么要做的 */
					break;
				case 'S':		/* 参数状态 */
					if (fc_getParameterStatus(fc_conn))
						return;
					break;
				case 'K':		/* 来自后端的密钥数据 */

					/*
					 * 这只在后端启动期间是预期的，但将其作为主循环的一部分处理也同样容易。
					 * 保存数据并继续处理。
					 */
					if (pqGetInt(&(fc_conn->be_pid), 4, fc_conn))
						return;
					if (pqGetInt(&(fc_conn->be_key), 4, fc_conn))
						return;
					break;
				case 'T':		/* 行描述 */
					if (fc_conn->error_result ||
						(fc_conn->result != NULL &&
						 fc_conn->result->resultStatus == PGRES_FATAL_ERROR))
					{
						/*
						 * 我们已经因为某种原因卡住了。 只需丢弃
						 * 数据，直到我们到达查询的末尾。
						 */
						fc_conn->inCursor += fc_msgLength;
					}
					else if (fc_conn->result == NULL ||
							 (fc_conn->cmd_queue_head &&
							  fc_conn->cmd_queue_head->queryclass == PGQUERY_DESCRIBE))
					{
						/* 查询序列中的第一个'T' */
						if (fc_getRowDescriptions(fc_conn, fc_msgLength))
							return;
					}
					else
					{
						/*
						 * 新的'T'消息被视为另一PGresult的开始。
						 * （目前尚不清楚这是否真的可能。）我们停止
						 * 解析，直到应用程序接受当前
						 * 结果。
						 */
						fc_conn->asyncStatus = PGASYNC_READY;
						return;
					}
					break;
				case 'n':		/* 无数据 */

					/*
					 * NoData表示我们将不会看到一个
					 * RowDescription消息，因为查询的语句或门户
					 * 不返回行。
					 *
					 * 如果我们在做描述，我们必须返回一些东西
					 * 给客户端，所以设置一个COMMAND_OK结果，
					 * 而不是PGRES_TUPLES_OK。 否则我们可以只
					 * 忽略这个消息。
					 */
					if (fc_conn->cmd_queue_head &&
						fc_conn->cmd_queue_head->queryclass == PGQUERY_DESCRIBE)
					{
						if (!pgHavePendingResult(fc_conn))
						{
							fc_conn->result = PQmakeEmptyPGresult(fc_conn,
															   PGRES_COMMAND_OK);
							if (!fc_conn->result)
							{
								appendPQExpBufferStr(&fc_conn->errorMessage,
													 libpq_gettext("out of memory"));
								pqSaveErrorResult(fc_conn);
							}
						}
						fc_conn->asyncStatus = PGASYNC_READY;
					}
					break;
				case 't':		/* 参数描述 */
					if (fc_getParamDescriptions(fc_conn, fc_msgLength))
						return;
					break;
				case 'D':		/* 数据行 */
					if (fc_conn->result != NULL &&
						fc_conn->result->resultStatus == PGRES_TUPLES_OK)
					{
						/* 读取正常查询响应的另一个元组 */
						if (fc_getAnotherTuple(fc_conn, fc_msgLength))
							return;
					}
					else if (fc_conn->error_result ||
							 (fc_conn->result != NULL &&
							  fc_conn->result->resultStatus == PGRES_FATAL_ERROR))
					{
						/*
						 * 我们已经因为某种原因卡住了。 只需丢弃
						 * 元组，直到我们到达查询的末尾。
						 */
						fc_conn->inCursor += fc_msgLength;
					}
					else
					{
						/* 设置以在查询结束时报告错误 */
						appendPQExpBufferStr(&fc_conn->errorMessage,
											 libpq_gettext("server sent data (\"D\" message) without prior row description (\"T\" message)\n"));
						pqSaveErrorResult(fc_conn);
						/* 丢弃意外消息 */
						fc_conn->inCursor += fc_msgLength;
					}
					break;
				case 'G':		/* 开始复制输入 */
					if (fc_getCopyStart(fc_conn, PGRES_COPY_IN))
						return;
					fc_conn->asyncStatus = PGASYNC_COPY_IN;
					break;
				case 'H':		/* 开始复制输出 */
					if (fc_getCopyStart(fc_conn, PGRES_COPY_OUT))
						return;
					fc_conn->asyncStatus = PGASYNC_COPY_OUT;
					fc_conn->copy_already_done = 0;
					break;
				case 'W':		/* 开始双向复制 */
					if (fc_getCopyStart(fc_conn, PGRES_COPY_BOTH))
						return;
					fc_conn->asyncStatus = PGASYNC_COPY_BOTH;
					fc_conn->copy_already_done = 0;
					break;
				case 'd':		/* 复制数据 */

					/*
					 * 如果我们看到复制数据，只需静默丢弃它。 这只会
					 * 在应用程序过早退出COPY OUT模式时发生。
					 */
					fc_conn->inCursor += fc_msgLength;
					break;
				case 'c':		/* 复制完成 */

					/*
					 * 如果我们看到复制完成，只需静默丢弃它。 这是
					 * 在PQendcopy期间的正常情况。 我们将继续
					 * 吞掉数据，期望看到COPY命令的命令完成。
					 */
					break;
				default:
					appendPQExpBuffer(&fc_conn->errorMessage,
									  libpq_gettext("unexpected response from server; first received character was \"%c\"\n"),
									  fc_id);
					/* 构建一个包含错误消息的错误结果 */
					pqSaveErrorResult(fc_conn);
					/* 不确定我们是否会看到更多，所以进入就绪状态 */
					fc_conn->asyncStatus = PGASYNC_READY;
					/* 丢弃意外消息 */
					fc_conn->inCursor += fc_msgLength;
					break;
			}					/* 切换到协议字符 */
		}
		/* 成功处理了该消息 */
		if (fc_conn->inCursor == fc_conn->inStart + 5 + fc_msgLength)
		{
			/* 跟踪服务器到客户端的消息 */
			if (fc_conn->Pfdebug)
				pqTraceOutputMessage(fc_conn, fc_conn->inBuffer + fc_conn->inStart, false);

			/* 正常情况：解析与指定长度一致 */
			fc_conn->inStart = fc_conn->inCursor;
		}
		else
		{
			/* 出问题了 --- 报告它 */
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("message contents do not agree with length in message type \"%c\"\n"),
							  fc_id);
			/* 构建一个包含错误消息的错误结果 */
			pqSaveErrorResult(fc_conn);
			fc_conn->asyncStatus = PGASYNC_READY;
			/* 信任指定的消息长度作为跳过的依据 */
			fc_conn->inStart += 5 + fc_msgLength;
		}
	}
}

/*
 * handleSyncLoss: 在丢失消息边界同步后进行清理
 *
 * 这里我们能做的不多，除了放弃连接。
 */
static void fc_handleSyncLoss(PGconn *fc_conn, char fc_id, int fc_msgLength)
{
	appendPQExpBuffer(&fc_conn->errorMessage,
					  libpq_gettext("lost synchronization with server: got message type \"%c\", length %d\n"),
					  fc_id, fc_msgLength);
	/* 构建一个包含错误消息的错误结果 */
	pqSaveErrorResult(fc_conn);
	fc_conn->asyncStatus = PGASYNC_READY;	/* 退出PQgetResult等待循环 */
	/* 刷新输入数据，因为我们放弃了处理 */
	pqDropConnection(fc_conn, true);
	fc_conn->status = CONNECTION_BAD;	/* 不再与后端连接 */
}

/*
 * parseInput 子例程用于读取 'T'（行描述）信息。
 * 我们将构建一个新的 PGresult 结构（除非是为预处理语句调用 Describe 命令）来包含属性数据。
 * 返回值：0 表示成功处理消息，EOF 表示暂停解析
 * （后者目前实际上未使用）。
 */
static int fc_getRowDescriptions(PGconn *fc_conn, int fc_msgLength)
{
	PGresult   *fc_result;
	int			fc_nfields;
	const char *fc_errmsg;
	int			fc_i;

	/*
	 * 在为预处理语句执行 Describe 时，getParamDescriptions 已经创建了一个 PGresult，
	 * 我们应该向其中填充数据。否则，创建一个新的空 PGresult。
	 */
	if (!fc_conn->cmd_queue_head ||
		(fc_conn->cmd_queue_head &&
		 fc_conn->cmd_queue_head->queryclass == PGQUERY_DESCRIBE))
	{
		if (fc_conn->result)
			fc_result = fc_conn->result;
		else
			fc_result = PQmakeEmptyPGresult(fc_conn, PGRES_COMMAND_OK);
	}
	else
		fc_result = PQmakeEmptyPGresult(fc_conn, PGRES_TUPLES_OK);
	if (!fc_result)
	{
		fc_errmsg = NULL;			/* 意味着“内存不足”，见下文 */
		goto advance_and_error;
	}

	/* parseInput 已经读取了 'T' 标签和消息长度。 */
	/* 接下来的两个字节是字段的数量 */
	if (pqGetInt(&(fc_result->numAttributes), 2, fc_conn))
	{
		/* 我们在这里不应该数据耗尽，因此要抱怨 */
		fc_errmsg = libpq_gettext("insufficient data in \"T\" message");
		goto advance_and_error;
	}
	fc_nfields = fc_result->numAttributes;

	/* 为属性描述符分配空间 */
	if (fc_nfields > 0)
	{
		fc_result->attDescs = (PGresAttDesc *)
			pqResultAlloc(fc_result, fc_nfields * sizeof(PGresAttDesc), true);
		if (!fc_result->attDescs)
		{
			fc_errmsg = NULL;		/* 意味着“内存不足”，见下文 */
			goto advance_and_error;
		}
		MemSet(fc_result->attDescs, 0, fc_nfields * sizeof(PGresAttDesc));
	}

	/* result->binary 仅在所有列都是二进制时为真 */
	fc_result->binary = (fc_nfields > 0) ? 1 : 0;

	/* 获取类型信息 */
	for (fc_i = 0; fc_i < fc_nfields; fc_i++)
	{
		int			fc_tableid;
		int			fc_columnid;
		int			fc_typid;
		int			fc_typlen;
		int			fc_atttypmod;
		int			fc_format;

		if (pqGets(&fc_conn->workBuffer, fc_conn) ||
			pqGetInt(&fc_tableid, 4, fc_conn) ||
			pqGetInt(&fc_columnid, 2, fc_conn) ||
			pqGetInt(&fc_typid, 4, fc_conn) ||
			pqGetInt(&fc_typlen, 2, fc_conn) ||
			pqGetInt(&fc_atttypmod, 4, fc_conn) ||
			pqGetInt(&fc_format, 2, fc_conn))
		{
			/* 我们在这里不应该数据耗尽，因此要抱怨 */
			fc_errmsg = libpq_gettext("insufficient data in \"T\" message");
			goto advance_and_error;
		}

		/*
		 * 由于 pqGetInt 将 2 字节整数视为无符号，我们需要
		 * 将这些结果强制转换为有符号形式。
		 */
		fc_columnid = (int) ((int16) fc_columnid);
		fc_typlen = (int) ((int16) fc_typlen);
		fc_format = (int) ((int16) fc_format);

		fc_result->attDescs[fc_i].name = pqResultStrdup(fc_result,
												  fc_conn->workBuffer.data);
		if (!fc_result->attDescs[fc_i].name)
		{
			fc_errmsg = NULL;		/* 意味着“内存不足”，见下文 */
			goto advance_and_error;
		}
		fc_result->attDescs[fc_i].tableid = fc_tableid;
		fc_result->attDescs[fc_i].columnid = fc_columnid;
		fc_result->attDescs[fc_i].format = fc_format;
		fc_result->attDescs[fc_i].typid = fc_typid;
		fc_result->attDescs[fc_i].typlen = fc_typlen;
		fc_result->attDescs[fc_i].atttypmod = fc_atttypmod;

		if (fc_format != 1)
			fc_result->binary = 0;
	}

	/* 成功！ */
	fc_conn->result = fc_result;

	/*
	 * 如果我们正在执行 Describe，那么我们完成了，并准备将结果
	 * 传递回客户端。
	 */
	if ((!fc_conn->cmd_queue_head) ||
		(fc_conn->cmd_queue_head &&
		 fc_conn->cmd_queue_head->queryclass == PGQUERY_DESCRIBE))
	{
		fc_conn->asyncStatus = PGASYNC_READY;
		return 0;
	}

	/*
	 * 我们可以在这里为新的结果集执行额外的设置，但目前没有别的工作要做。
	 */

	/* 我们完成了。 */
	return 0;

advance_and_error:
	/* 如果有未保存的结果，则丢弃 */
	if (fc_result && fc_result != fc_conn->result)
		PQclear(fc_result);

	/*
	 * 用错误结果替换部分构造的结果。首先丢弃旧结果以尝试回收一些内存。
	 */
	pqClearAsyncResult(fc_conn);

	/*
	 * 如果前面的代码没有提供错误消息，则假定是“内存不足”。
	 * 拥有这种特例的好处是，首先释放旧结果大大提高了 gettext()
	 * 成功提供翻译的概率。
	 */
	if (!fc_errmsg)
		fc_errmsg = libpq_gettext("out of memory for query result");

	appendPQExpBuffer(&fc_conn->errorMessage, "%s\n", fc_errmsg);
	pqSaveErrorResult(fc_conn);

	/*
	 * 将消息显示为已完全消耗，否则 pqParseInput3 会用
	 * 对此的抱怨覆盖我们的错误。
	 */
	fc_conn->inCursor = fc_conn->inStart + 5 + fc_msgLength;

	/*
	 * 返回零以允许输入解析继续。后续的 "D"
	 * 消息将在我们到达数据结束之前被忽略，因为已经设置了错误结果。
	 */
	return 0;
}

/*
 * parseInput 子例程用于读取 't'（参数描述）消息。
 * 我们将构建一个新的 PGresult 结构，包含参数数据。
 * 返回值：0 表示成功处理消息，EOF 表示暂停解析
 * （后者目前实际上未使用）。
 */
static int fc_getParamDescriptions(PGconn *fc_conn, int fc_msgLength)
{
	PGresult   *fc_result;
	const char *fc_errmsg = NULL;	/* 意味着“内存不足”，见下文 */
	int			fc_nparams;
	int			fc_i;

	fc_result = PQmakeEmptyPGresult(fc_conn, PGRES_COMMAND_OK);
	if (!fc_result)
		goto advance_and_error;

	/* parseInput 已经读取了 't' 标签和消息长度。 */
	/* 接下来的两个字节是参数的数量 */
	if (pqGetInt(&(fc_result->numParameters), 2, fc_conn))
		goto not_enough_data;
	fc_nparams = fc_result->numParameters;

	/* 为参数描述符分配空间 */
	if (fc_nparams > 0)
	{
		fc_result->paramDescs = (PGresParamDesc *)
			pqResultAlloc(fc_result, fc_nparams * sizeof(PGresParamDesc), true);
		if (!fc_result->paramDescs)
			goto advance_and_error;
		MemSet(fc_result->paramDescs, 0, fc_nparams * sizeof(PGresParamDesc));
	}

	/* 获取参数信息 */
	for (fc_i = 0; fc_i < fc_nparams; fc_i++)
	{
		int			fc_typid;

		if (pqGetInt(&fc_typid, 4, fc_conn))
			goto not_enough_data;
		fc_result->paramDescs[fc_i].typid = fc_typid;
	}

	/* 成功！ */
	fc_conn->result = fc_result;

	return 0;

not_enough_data:
	fc_errmsg = libpq_gettext("insufficient data in \"t\" message");

advance_and_error:
	/* 如果有未保存的结果，则丢弃 */
	if (fc_result && fc_result != fc_conn->result)
		PQclear(fc_result);

	/*
	 * 用错误结果替换部分构造的结果。首先丢弃旧结果以尝试回收一些内存。
	 */
	pqClearAsyncResult(fc_conn);

	/*
	 * 如果前面的代码没有提供错误消息，则假定是“内存不足”。
	 * 拥有这种特例的好处是，首先释放旧结果大大提高了 gettext()
	 * 成功提供翻译的概率。
	 */
	if (!fc_errmsg)
		fc_errmsg = libpq_gettext("out of memory");
	appendPQExpBuffer(&fc_conn->errorMessage, "%s\n", fc_errmsg);
	pqSaveErrorResult(fc_conn);

	/*
	 * 将消息显示为已完全消耗，否则 pqParseInput3 会用
	 * 对此的抱怨覆盖我们的错误。
	 */
	fc_conn->inCursor = fc_conn->inStart + 5 + fc_msgLength;

	/*
	 * 返回零以允许输入解析继续。 本质上，我们将 COMMAND_OK 结果替换为
	 * 错误结果，但由于这不会影响协议状态，因此没问题。
	 */
	return 0;
}

/*
 * parseInput 子程序以读取 'D'（行数据）消息。
 * 我们用列指针填充 rowbuf，然后调用行处理程序。
 * 返回：如果成功处理消息则为 0，EOF 则中止解析
 * （后一种情况目前实际上未被使用）。
 */
static int fc_getAnotherTuple(PGconn *fc_conn, int fc_msgLength)
{
	PGresult   *fc_result = fc_conn->result;
	int			fc_nfields = fc_result->numAttributes;
	const char *fc_errmsg;
	PGdataValue *fc_rowbuf;
	int			fc_tupnfields;		/* 来自元组的字段数量 */
	int			fc_vlen;			/* 当前字段值的长度 */
	int			fc_i;

	/* 获取字段计数并确保它是我们预期的 */
	if (pqGetInt(&fc_tupnfields, 2, fc_conn))
	{
		/* 我们在这里不应该数据耗尽，因此要抱怨 */
		fc_errmsg = libpq_gettext("insufficient data in \"D\" message");
		goto advance_and_error;
	}

	if (fc_tupnfields != fc_nfields)
	{
		fc_errmsg = libpq_gettext("unexpected field count in \"D\" message");
		goto advance_and_error;
	}

	/* 如有必要，调整行缓冲区大小 */
	fc_rowbuf = fc_conn->rowBuf;
	if (fc_nfields > fc_conn->rowBufLen)
	{
		fc_rowbuf = (PGdataValue *) realloc(fc_rowbuf,
										 fc_nfields * sizeof(PGdataValue));
		if (!fc_rowbuf)
		{
			fc_errmsg = NULL;		/* 意味着“内存不足”，见下文 */
			goto advance_and_error;
		}
		fc_conn->rowBuf = fc_rowbuf;
		fc_conn->rowBufLen = fc_nfields;
	}

	/* 扫描字段 */
	for (fc_i = 0; fc_i < fc_nfields; fc_i++)
	{
		/* 获取值长度 */
		if (pqGetInt(&fc_vlen, 4, fc_conn))
		{
			/* 我们在这里不应该数据耗尽，因此要抱怨 */
			fc_errmsg = libpq_gettext("insufficient data in \"D\" message");
			goto advance_and_error;
		}
		fc_rowbuf[fc_i].len = fc_vlen;

		/*
		 * rowbuf[i].value 始终指向数据缓冲区中的下一个地址，即使值为 NULL。
		 * 这允许行处理程序更容易地估算数据大小。
		 */
		fc_rowbuf[fc_i].value = fc_conn->inBuffer + fc_conn->inCursor;

		/* 跳过数据值 */
		if (fc_vlen > 0)
		{
			if (pqSkipnchar(fc_vlen, fc_conn))
			{
				/* 我们在这里不应该数据耗尽，因此要抱怨 */
				fc_errmsg = libpq_gettext("insufficient data in \"D\" message");
				goto advance_and_error;
			}
		}
	}

	/* 处理收集到的行 */
	fc_errmsg = NULL;
	if (pqRowProcessor(fc_conn, &fc_errmsg))
		return 0;				/* 正常、成功退出 */

	/* pqRowProcessor 失败，掉落以报告它 */

advance_and_error:

	/*
	 * 用错误结果替换部分构造的结果。首先丢弃旧结果以尝试回收一些内存。
	 */
	pqClearAsyncResult(fc_conn);

	/*
	 * 如果前面的代码没有提供错误消息，则假定是“内存不足”。
	 * 拥有这种特例的好处是，首先释放旧结果大大提高了 gettext()
	 * 成功提供翻译的概率。
	 */
	if (!fc_errmsg)
		fc_errmsg = libpq_gettext("out of memory for query result");

	appendPQExpBuffer(&fc_conn->errorMessage, "%s\n", fc_errmsg);
	pqSaveErrorResult(fc_conn);

	/*
	 * 将消息显示为已完全消耗，否则 pqParseInput3 会用
	 * 对此的抱怨覆盖我们的错误。
	 */
	fc_conn->inCursor = fc_conn->inStart + 5 + fc_msgLength;

	/*
	 * 返回零以允许输入解析继续。后续的 "D"
	 * 消息将在我们到达数据结束之前被忽略，因为已经设置了错误结果。
	 */
	return 0;
}


/*
 * 尝试读取错误或通知响应消息。
 * 这是在多个地方可能的，因此我们将其分解为一个子例程。
 * 入口：'E' 或 'N' 消息类型和长度已被消耗。
 * 出口：如果成功消耗消息则返回 0。
 *		 如果数据不足则返回 EOF。
 */
int pqGetErrorNotice3(PGconn *fc_conn, bool fc_isError)
{
	PGresult   *fc_res = NULL;
	bool		fc_have_position = false;
	PQExpBufferData fc_workBuf;
	char		fc_id;

	/* 如果处于管道模式，设置错误指示符 */
	if (fc_isError && fc_conn->pipelineStatus != PQ_PIPELINE_OFF)
		fc_conn->pipelineStatus = PQ_PIPELINE_ABORTED;

	/*
	 * 如果这是一个错误消息，预先清除我们可能拥有的任何未完成的查询结果。
	 * 我们在下面反正会抛弃它，提前释放可能避免内存不足。
	 */
	if (fc_isError)
		pqClearAsyncResult(fc_conn);

	/*
	 * 由于字段可能相当长，我们创建一个临时的 PQExpBuffer，而不是使用 conn->workBuffer。
	 * workBuffer 旨在用于预期较短的内容。我们也不应该使用 conn->errorMessage，
	 * 因为这可能只是一个通知。
	 */
	initPQExpBuffer(&fc_workBuf);

	/*
	 * 创建一个 PGresult 来保存累积的字段。我们暂时对结果状态撒谎，
	 * 以便 PQmakeEmptyPGresult 不会无用地复制 conn->errorMessage。
	 *
	 * 注意：如果你耗尽内存，此分配可能会失败。该函数的其余部分优雅地处理这一情况，
	 * 我们仍然尝试将错误消息设置为连接的错误消息。
	 */
	fc_res = PQmakeEmptyPGresult(fc_conn, PGRES_EMPTY_QUERY);
	if (fc_res)
		fc_res->resultStatus = fc_isError ? PGRES_FATAL_ERROR : PGRES_NONFATAL_ERROR;

	/*
	 * 读取字段并保存到 res 中。
	 *
	 * 在此过程中，将 SQLSTATE 保存到 conn->last_sqlstate，并指出我们是否看到了 PG_DIAG_STATEMENT_POSITION 字段。
	 */
	for (;;)
	{
		if (pqGetc(&fc_id, fc_conn))
			goto fail;
		if (fc_id == '\0')
			break;				/* 找到终止符 */
		if (pqGets(&fc_workBuf, fc_conn))
			goto fail;
		pqSaveMessageField(fc_res, fc_id, fc_workBuf.data);
		if (fc_id == PG_DIAG_SQLSTATE)
			strlcpy(fc_conn->last_sqlstate, fc_workBuf.data,
					sizeof(fc_conn->last_sqlstate));
		else if (fc_id == PG_DIAG_STATEMENT_POSITION)
			fc_have_position = true;
	}

	/*
	 * 将活动查询文本（如果有）也保存到 res 中；但只有在我们可能需要它用于错误光标显示的情况下，
	 * 这只有在存在 PG_DIAG_STATEMENT_POSITION 字段时才为真。
	 */
	if (fc_have_position && fc_res && fc_conn->cmd_queue_head && fc_conn->cmd_queue_head->query)
		fc_res->errQuery = pqResultStrdup(fc_res, fc_conn->cmd_queue_head->query);

	/*
	 * 现在为 PQresultErrorMessage 构建“总体”错误消息。
	 */
	resetPQExpBuffer(&fc_workBuf);
	pqBuildErrorMessage3(&fc_workBuf, fc_res, fc_conn->verbosity, fc_conn->show_context);

	/*
	 * 要么将错误保存为当前的异步结果，要么仅仅发出通知。
	 */
	if (fc_isError)
	{
		pqClearAsyncResult(fc_conn);	/* 多余，但安全 */
		if (fc_res)
		{
			pqSetResultError(fc_res, &fc_workBuf, 0);
			fc_conn->result = fc_res;
		}
		else
		{
			/* 退回到使用内部错误处理路径 */
			fc_conn->error_result = true;
		}

		if (PQExpBufferDataBroken(fc_workBuf))
			appendPQExpBufferStr(&fc_conn->errorMessage,
								 libpq_gettext("out of memory\n"));
		else
			appendPQExpBufferStr(&fc_conn->errorMessage, fc_workBuf.data);
	}
	else
	{
		/* 如果我们无法分配结果集，只需丢弃通知 */
		if (fc_res)
		{
			/*
			 * 我们可以在这里稍微作弊，不复制消息。
			 * 但如果我们在填充 workBuf 的时候不幸耗尽内存，插入“内存不足”，如在 pqSetResultError 中所示。
			 */
			if (PQExpBufferDataBroken(fc_workBuf))
				fc_res->errMsg = libpq_gettext("out of memory\n");
			else
				fc_res->errMsg = fc_workBuf.data;
			if (fc_res->noticeHooks.noticeRec != NULL)
				fc_res->noticeHooks.noticeRec(fc_res->noticeHooks.noticeRecArg, fc_res);
			PQclear(fc_res);
		}
	}

	termPQExpBuffer(&fc_workBuf);
	return 0;

fail:
	PQclear(fc_res);
	termPQExpBuffer(&fc_workBuf);
	return EOF;
}

/*
 * 从给定 PGresult 中的字段构造一个错误消息，
 * 并将其附加到 "msg" 的内容中。
 */
void pqBuildErrorMessage3(PQExpBuffer fc_msg, const PGresult *fc_res,
					 PGVerbosity fc_verbosity, PGContextVisibility fc_show_context)
{
	const char *fc_val;
	const char *fc_querytext = NULL;
	int			fc_querypos = 0;

	/* 如果我们无法分配 PGresult，只需说“内存不足” */
	if (fc_res == NULL)
	{
		appendPQExpBufferStr(fc_msg, libpq_gettext("out of memory\n"));
		return;
	}

	/*
	 * 如果我们没有任何分解的字段，只需返回基本消息。
	 * 这主要适用于我们得到一个 libpq 生成的错误结果的情况。
	 */
	if (fc_res->errFields == NULL)
	{
		if (fc_res->errMsg && fc_res->errMsg[0])
			appendPQExpBufferStr(fc_msg, fc_res->errMsg);
		else
			appendPQExpBufferStr(fc_msg, libpq_gettext("no error message available\n"));
		return;
	}

	/* 否则从相关字段构建错误消息 */
	fc_val = PQresultErrorField(fc_res, PG_DIAG_SEVERITY);
	if (fc_val)
		appendPQExpBuffer(fc_msg, "%s:  ", fc_val);

	if (fc_verbosity == PQERRORS_SQLSTATE)
	{
		/*
		 * 如果我们有 SQLSTATE，只打印它而不打印其他内容。
		 * 如果没有（这对服务器生成的错误不应该发生，但对 libpq 生成的错误可能会发生），
		 * 采用 TERSE 格式，因为这似乎比什么都不打印要好。
		 */
		fc_val = PQresultErrorField(fc_res, PG_DIAG_SQLSTATE);
		if (fc_val)
		{
			appendPQExpBuffer(fc_msg, "%s\n", fc_val);
			return;
		}
		fc_verbosity = PQERRORS_TERSE;
	}

	if (fc_verbosity == PQERRORS_VERBOSE)
	{
		fc_val = PQresultErrorField(fc_res, PG_DIAG_SQLSTATE);
		if (fc_val)
			appendPQExpBuffer(fc_msg, "%s: ", fc_val);
	}
	fc_val = PQresultErrorField(fc_res, PG_DIAG_MESSAGE_PRIMARY);
	if (fc_val)
		appendPQExpBufferStr(fc_msg, fc_val);
	fc_val = PQresultErrorField(fc_res, PG_DIAG_STATEMENT_POSITION);
	if (fc_val)
	{
		if (fc_verbosity != PQERRORS_TERSE && fc_res->errQuery != NULL)
		{
			/* 将位置作为语法光标显示 */
			fc_querytext = fc_res->errQuery;
			fc_querypos = atoi(fc_val);
		}
		else
		{
			/* 将位置作为文本附加到主要消息 */
			/* 翻译者：%s 代表数字字符串 */
			appendPQExpBuffer(fc_msg, libpq_gettext(" at character %s"),
							  fc_val);
		}
	}
	else
	{
		fc_val = PQresultErrorField(fc_res, PG_DIAG_INTERNAL_POSITION);
		if (fc_val)
		{
			fc_querytext = PQresultErrorField(fc_res, PG_DIAG_INTERNAL_QUERY);
			if (fc_verbosity != PQERRORS_TERSE && fc_querytext != NULL)
			{
				/* 将位置作为语法光标显示 */
				fc_querypos = atoi(fc_val);
			}
			else
			{
				/* 将位置作为文本附加到主要消息 */
				/* 翻译者：%s 代表数字字符串 */
				appendPQExpBuffer(fc_msg, libpq_gettext(" at character %s"),
								  fc_val);
			}
		}
	}
	appendPQExpBufferChar(fc_msg, '\n');
	if (fc_verbosity != PQERRORS_TERSE)
	{
		if (fc_querytext && fc_querypos > 0)
			fc_reportErrorPosition(fc_msg, fc_querytext, fc_querypos,
								fc_res->client_encoding);
		fc_val = PQresultErrorField(fc_res, PG_DIAG_MESSAGE_DETAIL);
		if (fc_val)
			appendPQExpBuffer(fc_msg, libpq_gettext("DETAIL:  %s\n"), fc_val);
		fc_val = PQresultErrorField(fc_res, PG_DIAG_MESSAGE_HINT);
		if (fc_val)
			appendPQExpBuffer(fc_msg, libpq_gettext("HINT:  %s\n"), fc_val);
		fc_val = PQresultErrorField(fc_res, PG_DIAG_INTERNAL_QUERY);
		if (fc_val)
			appendPQExpBuffer(fc_msg, libpq_gettext("QUERY:  %s\n"), fc_val);
		if (fc_show_context == PQSHOW_CONTEXT_ALWAYS ||
			(fc_show_context == PQSHOW_CONTEXT_ERRORS &&
			 fc_res->resultStatus == PGRES_FATAL_ERROR))
		{
			fc_val = PQresultErrorField(fc_res, PG_DIAG_CONTEXT);
			if (fc_val)
				appendPQExpBuffer(fc_msg, libpq_gettext("CONTEXT:  %s\n"),
								  fc_val);
		}
	}
	if (fc_verbosity == PQERRORS_VERBOSE)
	{
		fc_val = PQresultErrorField(fc_res, PG_DIAG_SCHEMA_NAME);
		if (fc_val)
			appendPQExpBuffer(fc_msg,
							  libpq_gettext("SCHEMA NAME:  %s\n"), fc_val);
		fc_val = PQresultErrorField(fc_res, PG_DIAG_TABLE_NAME);
		if (fc_val)
			appendPQExpBuffer(fc_msg,
							  libpq_gettext("TABLE NAME:  %s\n"), fc_val);
		fc_val = PQresultErrorField(fc_res, PG_DIAG_COLUMN_NAME);
		if (fc_val)
			appendPQExpBuffer(fc_msg,
							  libpq_gettext("COLUMN NAME:  %s\n"), fc_val);
		fc_val = PQresultErrorField(fc_res, PG_DIAG_DATATYPE_NAME);
		if (fc_val)
			appendPQExpBuffer(fc_msg,
							  libpq_gettext("DATATYPE NAME:  %s\n"), fc_val);
		fc_val = PQresultErrorField(fc_res, PG_DIAG_CONSTRAINT_NAME);
		if (fc_val)
			appendPQExpBuffer(fc_msg,
							  libpq_gettext("CONSTRAINT NAME:  %s\n"), fc_val);
	}
	if (fc_verbosity == PQERRORS_VERBOSE)
	{
		const char *fc_valf;
		const char *fc_vall;

		fc_valf = PQresultErrorField(fc_res, PG_DIAG_SOURCE_FILE);
		fc_vall = PQresultErrorField(fc_res, PG_DIAG_SOURCE_LINE);
		fc_val = PQresultErrorField(fc_res, PG_DIAG_SOURCE_FUNCTION);
		if (fc_val || fc_valf || fc_vall)
		{
			appendPQExpBufferStr(fc_msg, libpq_gettext("LOCATION:  "));
			if (fc_val)
				appendPQExpBuffer(fc_msg, libpq_gettext("%s, "), fc_val);
			if (fc_valf && fc_vall)	/* 不太可能只有一个 */
				appendPQExpBuffer(fc_msg, libpq_gettext("%s:%s"),
								  fc_valf, fc_vall);
			appendPQExpBufferChar(fc_msg, '\n');
		}
	}
}

/*
 * 在正在构建的错误消息下添加错误位置显示。
 *
 * 光标位置以逻辑字符来衡量；查询字符串
 * 假定采用指定的编码。
 */
static void fc_reportErrorPosition(PQExpBuffer fc_msg, const char *fc_query, int fc_loc, int fc_encoding)
{
#define DISPLAY_SIZE	60		/* 屏幕宽度限制，以屏幕列为单位 */
#define MIN_RIGHT_CUT	10		/* 尽量与行尾保持一定距离 */

	char	   *fc_wquery;
	int			fc_slen,
				fc_cno,
				fc_i,
			   *fc_qidx,
			   *fc_scridx,
				fc_qoffset,
				fc_scroffset,
				fc_ibeg,
				fc_iend,
				fc_loc_line;
	bool		fc_mb_encoding,
				fc_beg_trunc,
				fc_end_trunc;

	/* 将 loc 从 1 基础转换为 0 基础；如果超出范围则不操作 */
	fc_loc--;
	if (fc_loc < 0)
		return;

	/* 需要查询的可写副本 */
	fc_wquery = strdup(fc_query);
	if (fc_wquery == NULL)
		return;					/* 如果内存不足则静默失败 */

	/*
	 * 每个字符在字符串中可能占用多个物理字节，在某些远东字符集中，可能也会占用多个屏幕列。
	 * 我们计算每个逻辑字符的起始字节偏移量和起始屏幕列，并将其分别存储在 qidx[] 和 scridx[] 中。
	 */

	/* 我们需要一个安全的分配大小... */
	fc_slen = strlen(fc_wquery) + 1;

	fc_qidx = (int *) malloc(fc_slen * sizeof(int));
	if (fc_qidx == NULL)
	{
		free(fc_wquery);
		return;
	}
	fc_scridx = (int *) malloc(fc_slen * sizeof(int));
	if (fc_scridx == NULL)
	{
		free(fc_qidx);
		free(fc_wquery);
		return;
	}

	/* 如果是单字节编码，我们可以稍微优化一下 */
	fc_mb_encoding = (pg_encoding_max_length(fc_encoding) != 1);

	/*
	 * 在扫描循环中，cno 是当前字符的逻辑编号，qoffset 是它在 wquery 中的偏移量，
	 * scroffset 是它的起始逻辑屏幕列（均以 0 为索引）。"loc" 是错误位置的逻辑字符编号。
	 * 我们扫描以确定 loc_line（包含 loc 的行的 1 基础行编号）和 ibeg/iend（包含 loc 的行的第一个字符
	 * 编号和最后+1个字符编号）。注意 qidx[] 和 scridx[] 仅填充到 iend。
	 */
	fc_qoffset = 0;
	fc_scroffset = 0;
	fc_loc_line = 1;
	fc_ibeg = 0;
	fc_iend = -1;					/* -1 意味着尚未设置 */

	for (fc_cno = 0; fc_wquery[fc_qoffset] != '\0'; fc_cno++)
	{
		char		fc_ch = fc_wquery[fc_qoffset];

		fc_qidx[fc_cno] = fc_qoffset;
		fc_scridx[fc_cno] = fc_scroffset;

		/*
		 * 在可写副本中用空格替换制表符。（稍后我们可能想考虑处理它们的可变屏幕宽度，但
		 * 不是今天。）
		 */
		if (fc_ch == '\t')
			fc_wquery[fc_qoffset] = ' ';

		/*
		 * 如果是行尾，计数行并标记位置。每个 \r 或 \n
		 * 都算作一行，除非 \r \n 同时出现。
		 */
		else if (fc_ch == '\r' || fc_ch == '\n')
		{
			if (fc_cno < fc_loc)
			{
				if (fc_ch == '\r' ||
					fc_cno == 0 ||
					fc_wquery[fc_qidx[fc_cno - 1]] != '\r')
					fc_loc_line++;
				/* 提取开始 = loc 之前最后一行的开始。 */
				fc_ibeg = fc_cno + 1;
			}
			else
			{
				/* 设置提取结束。 */
				fc_iend = fc_cno;
				/* 扫描完成。 */
				break;
			}
		}

		/* 前进 */
		if (fc_mb_encoding)
		{
			int			fc_w;

			fc_w = pg_encoding_dsplen(fc_encoding, &fc_wquery[fc_qoffset]);
			/* 将任何非制表符控制字符视为宽度 1 */
			if (fc_w <= 0)
				fc_w = 1;
			fc_scroffset += fc_w;
			fc_qoffset += PQmblenBounded(&fc_wquery[fc_qoffset], fc_encoding);
		}
		else
		{
			/* 我们假设宽字符仅存在于多字节编码中 */
			fc_scroffset++;
			fc_qoffset++;
		}
	}
	/* 如果在 loc 后没有找到行尾，则进行修正 */
	if (fc_iend < 0)
	{
		fc_iend = fc_cno;				/* 查询长度（以字符计），+1 */
		fc_qidx[fc_iend] = fc_qoffset;
		fc_scridx[fc_iend] = fc_scroffset;
	}

	/* 仅在 loc 在计算的查询长度范围内时打印 */
	if (fc_loc <= fc_cno)
	{
		/* 如果提取的行太长，我们将其截断。 */
		fc_beg_trunc = false;
		fc_end_trunc = false;
		if (fc_scridx[fc_iend] - fc_scridx[fc_ibeg] > DISPLAY_SIZE)
		{
			/*
			 * 我们首先在右侧进行截断，如果足够的话。该代码可能在强制 MIN_RIGHT_CUT 时
			 * 会偏离一个空格左右，特别是在那有一个宽字符时，但那应该没问题。
			 */
			if (fc_scridx[fc_ibeg] + DISPLAY_SIZE >= fc_scridx[fc_loc] + MIN_RIGHT_CUT)
			{
				while (fc_scridx[fc_iend] - fc_scridx[fc_ibeg] > DISPLAY_SIZE)
					fc_iend--;
				fc_end_trunc = true;
			}
			else
			{
				/* 如果离 loc 不太近则在右侧截断。 */
				while (fc_scridx[fc_loc] + MIN_RIGHT_CUT < fc_scridx[fc_iend])
				{
					fc_iend--;
					fc_end_trunc = true;
				}

				/* 如果仍然太长则在左侧截断。 */
				while (fc_scridx[fc_iend] - fc_scridx[fc_ibeg] > DISPLAY_SIZE)
				{
					fc_ibeg++;
					fc_beg_trunc = true;
				}
			}
		}

		/* 在所需的端点截断工作副本 */
		fc_wquery[fc_qidx[fc_iend]] = '\0';

		/* 开始构建完成的消息。 */
		fc_i = fc_msg->len;
		appendPQExpBuffer(fc_msg, libpq_gettext("LINE %d: "), fc_loc_line);
		if (fc_beg_trunc)
			appendPQExpBufferStr(fc_msg, "...");

		/*
		 * 当我们在 msg 缓冲区中有前缀时，计算其屏幕宽度。
		 */
		fc_scroffset = 0;
		for (; fc_i < fc_msg->len; fc_i += PQmblenBounded(&fc_msg->data[fc_i], fc_encoding))
		{
			int			fc_w = pg_encoding_dsplen(fc_encoding, &fc_msg->data[fc_i]);

			if (fc_w <= 0)
				fc_w = 1;
			fc_scroffset += fc_w;
		}

		/* 完成 LINE 消息行。 */
		appendPQExpBufferStr(fc_msg, &fc_wquery[fc_qidx[fc_ibeg]]);
		if (fc_end_trunc)
			appendPQExpBufferStr(fc_msg, "...");
		appendPQExpBufferChar(fc_msg, '\n');

		/* 现在发出光标标记行。 */
		fc_scroffset += fc_scridx[fc_loc] - fc_scridx[fc_ibeg];
		for (fc_i = 0; fc_i < fc_scroffset; fc_i++)
			appendPQExpBufferChar(fc_msg, ' ');
		appendPQExpBufferChar(fc_msg, '^');
		appendPQExpBufferChar(fc_msg, '\n');
	}

	/* 清理。 */
	free(fc_scridx);
	free(fc_qidx);
	free(fc_wquery);
}


/*
 * 尝试读取 ParameterStatus 消息。
 * 这可以在多个地方进行，因此我们将其拆分为一个子例程。
 * 入口：'S' 消息类型和长度已经被处理。
 * 退出：如果成功处理消息，返回 0。
 *		 如果数据不足，返回 EOF。
 */
static int fc_getParameterStatus(PGconn *fc_conn)
{
	PQExpBufferData fc_valueBuf;

	/* 获取参数名称 */
	if (pqGets(&fc_conn->workBuffer, fc_conn))
		return EOF;
	/* 获取参数值（可能很大） */
	initPQExpBuffer(&fc_valueBuf);
	if (pqGets(&fc_valueBuf, fc_conn))
	{
		termPQExpBuffer(&fc_valueBuf);
		return EOF;
	}
	/* 并保存它 */
	pqSaveParameterStatus(fc_conn, fc_conn->workBuffer.data, fc_valueBuf.data);
	termPQExpBuffer(&fc_valueBuf);
	return 0;
}


/*
 * 尝试读取 Notify 响应消息。
 * 这可以在多个地方进行，因此我们将其拆分为一个子例程。
 * 入口：'A' 消息类型和长度已经被处理。
 * 退出：如果成功处理 Notify 消息，返回 0。
 *		 如果数据不足，返回 EOF。
 */
static int fc_getNotify(PGconn *fc_conn)
{
	int			fc_be_pid;
	char	   *fc_svname;
	int			fc_nmlen;
	int			fc_extralen;
	PGnotify   *fc_newNotify;

	if (pqGetInt(&fc_be_pid, 4, fc_conn))
		return EOF;
	if (pqGets(&fc_conn->workBuffer, fc_conn))
		return EOF;
	/* 在获取额外字符串时必须保存名称 */
	fc_svname = strdup(fc_conn->workBuffer.data);
	if (!fc_svname)
		return EOF;
	if (pqGets(&fc_conn->workBuffer, fc_conn))
	{
		free(fc_svname);
		return EOF;
	}

	/*
	 * 在 PQnotify 结构之后存储字符串，以便可以一次性释放它们。
	 * 我们不使用 NAMEDATALEN，因为我们不想将此接口绑定到特定的服务器名称长度。
	 */
	fc_nmlen = strlen(fc_svname);
	fc_extralen = strlen(fc_conn->workBuffer.data);
	fc_newNotify = (PGnotify *) malloc(sizeof(PGnotify) + fc_nmlen + fc_extralen + 2);
	if (fc_newNotify)
	{
		fc_newNotify->relname = (char *) fc_newNotify + sizeof(PGnotify);
		strcpy(fc_newNotify->relname, fc_svname);
		fc_newNotify->extra = fc_newNotify->relname + fc_nmlen + 1;
		strcpy(fc_newNotify->extra, fc_conn->workBuffer.data);
		fc_newNotify->be_pid = fc_be_pid;
		fc_newNotify->next = NULL;
		if (fc_conn->notifyTail)
			fc_conn->notifyTail->next = fc_newNotify;
		else
			fc_conn->notifyHead = fc_newNotify;
		fc_conn->notifyTail = fc_newNotify;
	}

	free(fc_svname);
	return 0;
}

/*
 * getCopyStart - 处理 CopyInResponse、CopyOutResponse 或
 * CopyBothResponse 消息
 *
 * parseInput 已经读取了消息类型和长度。
 */
static int fc_getCopyStart(PGconn *fc_conn, ExecStatusType fc_copytype)
{
	PGresult   *fc_result;
	int			fc_nfields;
	int			fc_i;

	fc_result = PQmakeEmptyPGresult(fc_conn, fc_copytype);
	if (!fc_result)
		goto failure;

	if (pqGetc(&fc_conn->copy_is_binary, fc_conn))
		goto failure;
	fc_result->binary = fc_conn->copy_is_binary;
	/* 接下来的两个字节是字段数量 */
	if (pqGetInt(&(fc_result->numAttributes), 2, fc_conn))
		goto failure;
	fc_nfields = fc_result->numAttributes;

	/* 为属性描述符分配空间 */
	if (fc_nfields > 0)
	{
		fc_result->attDescs = (PGresAttDesc *)
			pqResultAlloc(fc_result, fc_nfields * sizeof(PGresAttDesc), true);
		if (!fc_result->attDescs)
			goto failure;
		MemSet(fc_result->attDescs, 0, fc_nfields * sizeof(PGresAttDesc));
	}

	for (fc_i = 0; fc_i < fc_nfields; fc_i++)
	{
		int			fc_format;

		if (pqGetInt(&fc_format, 2, fc_conn))
			goto failure;

		/*
		 * 由于 pqGetInt 将 2 字节整数视为无符号，我们需要
		 * 将这些结果强制转换为有符号形式。
		 */
		fc_format = (int) ((int16) fc_format);
		fc_result->attDescs[fc_i].format = fc_format;
	}

	/* 成功！ */
	fc_conn->result = fc_result;
	return 0;

failure:
	PQclear(fc_result);
	return EOF;
}

/*
 * getReadyForQuery - 处理 ReadyForQuery 消息
 */
static int fc_getReadyForQuery(PGconn *fc_conn)
{
	char		fc_xact_status;

	if (pqGetc(&fc_xact_status, fc_conn))
		return EOF;
	switch (fc_xact_status)
	{
		case 'I':
			fc_conn->xactStatus = PQTRANS_IDLE;
			break;
		case 'T':
			fc_conn->xactStatus = PQTRANS_INTRANS;
			break;
		case 'E':
			fc_conn->xactStatus = PQTRANS_INERROR;
			break;
		default:
			fc_conn->xactStatus = PQTRANS_UNKNOWN;
			break;
	}

	return 0;
}

/*
 * getCopyDataMessage - 获取下一个 CopyData 消息，处理异步消息
 *
 * 返回 CopyData 消息的长度字（> 0），如果没有完整的
 * 消息可用则返回 0，复制结束则返回 -1，错误则返回 -2。
 */
static int fc_getCopyDataMessage(PGconn *fc_conn)
{
	char		fc_id;
	int			fc_msgLength;
	int			fc_avail;

	for (;;)
	{
		/*
		 * 我们有下一个输入消息吗？为了简化异步调用者的生活，
		 * 我们不断返回 0，直到下一个消息完全可用，即使它不是 Copy Data。
		 */
		fc_conn->inCursor = fc_conn->inStart;
		if (pqGetc(&fc_id, fc_conn))
			return 0;
		if (pqGetInt(&fc_msgLength, 4, fc_conn))
			return 0;
		if (fc_msgLength < 4)
		{
			fc_handleSyncLoss(fc_conn, fc_id, fc_msgLength);
			return -2;
		}
		fc_avail = fc_conn->inEnd - fc_conn->inCursor;
		if (fc_avail < fc_msgLength - 4)
		{
			/*
			 * 在返回之前，如有必要扩大输入缓冲区以容纳
			 * 整个消息。请查看 parseInput 中的说明。
			 */
			if (pqCheckInBufferSpace(fc_conn->inCursor + (size_t) fc_msgLength - 4,
									 fc_conn))
			{
				/*
				 * XXX 添加一些更好的恢复代码...计划是跳过
				 * 消息，使用其长度，然后报告错误。此时，只需将其视为同步丢失
				 * （这确实可能是！）
				 */
				fc_handleSyncLoss(fc_conn, fc_id, fc_msgLength);
				return -2;
			}
			return 0;
		}

		/*
		 * 如果是合法的异步消息类型，处理它。（NOTIFY
		 * 消息目前在此处是不可能的，但我们为完整性处理它们。）否则，如果
		 * 它是除了 Copy Data 之外的任何东西，报告复制结束。
		 */
		switch (fc_id)
		{
			case 'A':			/* NOTIFY */
				if (fc_getNotify(fc_conn))
					return 0;
				break;
			case 'N':			/* NOTICE */
				if (pqGetErrorNotice3(fc_conn, false))
					return 0;
				break;
			case 'S':			/* ParameterStatus */
				if (fc_getParameterStatus(fc_conn))
					return 0;
				break;
			case 'd':			/* Copy Data，传回调用者 */
				return fc_msgLength;
			case 'c':

				/*
				 * 如果这是 CopyDone 消息，退出 COPY_OUT 模式并让
				 * 调用者通过 PQgetResult() 读取状态。如果我们在
				 * COPY_BOTH 模式中，返回到 COPY_IN 模式。
				 */
				if (fc_conn->asyncStatus == PGASYNC_COPY_BOTH)
					fc_conn->asyncStatus = PGASYNC_COPY_IN;
				else
					fc_conn->asyncStatus = PGASYNC_BUSY;
				return -1;
			default:			/* 视为复制结束 */

				/*
				 * 任何其他消息将终止 COPY_IN 或 COPY_BOTH
				 * 模式。
				 */
				fc_conn->asyncStatus = PGASYNC_BUSY;
				return -1;
		}

		/* 跟踪服务器到客户端的消息 */
		if (fc_conn->Pfdebug)
			pqTraceOutputMessage(fc_conn, fc_conn->inBuffer + fc_conn->inStart, false);

		/* 丢弃处理过的消息，并循环以处理另一个 */
		fc_conn->inStart = fc_conn->inCursor;
	}
}

/*
 * PQgetCopyData - 从后端读取COPY OUT或COPY BOTH过程中的一行数据
 *
 * 如果成功，设置 *buffer 指向一个 malloc 分配的行数据，并
 * 返回行长度（总是 > 0）作为结果。
 * 如果尚无可用行，则返回 0（仅在 async 为 true 时可能），
 * 如果复制结束则返回 -1（请参阅 PQgetResult），或如果错误则返回 -2（请参阅
 * PQerrorMessage）。
 */
int pqGetCopyData3(PGconn *fc_conn, char **fc_buffer, int fc_async)
{
	int			fc_msgLength;

	for (;;)
	{
		/*
		 * 收集下一个输入消息。为了简化async调用者的操作，
		 * 我们保持返回0，直到下一个消息完全可用，即使它不是Copy Data。
		 */
		fc_msgLength = fc_getCopyDataMessage(fc_conn);
		if (fc_msgLength < 0)
			return fc_msgLength;	/* 结束复制或错误 */
		if (fc_msgLength == 0)
		{
			/* 如果请求了异步读取 则不阻塞 */
			if (fc_async)
				return 0;
			/* 需要加载更多数据 */
			if (pqWait(true, false, fc_conn) ||
				pqReadData(fc_conn) < 0)
				return -2;
			continue;
		}

		/*
		 * 丢弃零长度消息（无论如何不应该发生）。否则
		 * 将数据传回给调用者。
		 */
		fc_msgLength -= 4;
		if (fc_msgLength > 0)
		{
			*fc_buffer = (char *) malloc(fc_msgLength + 1);
			if (*fc_buffer == NULL)
			{
				appendPQExpBufferStr(&fc_conn->errorMessage,
									 libpq_gettext("out of memory\n"));
				return -2;
			}
			memcpy(*fc_buffer, &fc_conn->inBuffer[fc_conn->inCursor], fc_msgLength);
			(*fc_buffer)[fc_msgLength] = '\0';	/* 添加终止空字符 */

			/* 标记消息已消耗 */
			fc_conn->inStart = fc_conn->inCursor + fc_msgLength;

			return fc_msgLength;
		}

		/* 为空，因此丢弃并循环回去获取下一个 */
		fc_conn->inStart = fc_conn->inCursor;
	}
}

/*
 * PQgetline - 从后端获取一个以换行符结束的字符串。
 *
 * 请参见fe-exec.c以获取文档。
 */
int pqGetline3(PGconn *fc_conn, char *fc_s, int fc_maxlen)
{
	int			fc_status;

	if (fc_conn->sock == PGINVALID_SOCKET ||
		(fc_conn->asyncStatus != PGASYNC_COPY_OUT &&
		 fc_conn->asyncStatus != PGASYNC_COPY_BOTH) ||
		fc_conn->copy_is_binary)
	{
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("PQgetline: not doing text COPY OUT\n"));
		*fc_s = '\0';
		return EOF;
	}

	while ((fc_status = PQgetlineAsync(fc_conn, fc_s, fc_maxlen - 1)) == 0)
	{
		/* 需要加载更多数据 */
		if (pqWait(true, false, fc_conn) ||
			pqReadData(fc_conn) < 0)
		{
			*fc_s = '\0';
			return EOF;
		}
	}

	if (fc_status < 0)
	{
		/* 检测到复制结束；生成旧式终止符 */
		strcpy(fc_s, "\\.");
		return 0;
	}

	/* 添加空终止符，并去掉末尾的\n（如果存在） */
	if (fc_s[fc_status - 1] == '\n')
	{
		fc_s[fc_status - 1] = '\0';
		return 0;
	}
	else
	{
		fc_s[fc_status] = '\0';
		return 1;
	}
}

/*
 * PQgetlineAsync - 获取一行COPY数据而不阻塞。
 *
 * 请参见fe-exec.c以获取文档。
 */
int pqGetlineAsync3(PGconn *fc_conn, char *fc_buffer, int fc_bufsize)
{
	int			fc_msgLength;
	int			fc_avail;

	if (fc_conn->asyncStatus != PGASYNC_COPY_OUT
		&& fc_conn->asyncStatus != PGASYNC_COPY_BOTH)
		return -1;				/* 我们并没有在进行复制... */

	/*
	 * 识别下一个输入消息。为了简化async调用者的操作，
	 * 我们保持返回0，直到下一个消息完全可用， 
	 * 即使它不是Copy Data。这应该可以防止PQendcopy阻塞。
	 * （注意：与pqGetCopyData3不同，我们在这里不更改asyncStatus。）
	 */
	fc_msgLength = fc_getCopyDataMessage(fc_conn);
	if (fc_msgLength < 0)
		return -1;				/* 结束复制或错误 */
	if (fc_msgLength == 0)
		return 0;				/* 还没有数据 */

	/*
	 * 将数据从libpq的缓冲区移动到调用者的缓冲区。在
	 * 先前调用发现调用者的缓冲区太小时，我们使用
	 * conn->copy_already_done来记住已经
	 * 返回给调用者的行的量。
	 */
	fc_conn->inCursor += fc_conn->copy_already_done;
	fc_avail = fc_msgLength - 4 - fc_conn->copy_already_done;
	if (fc_avail <= fc_bufsize)
	{
		/* 能够消耗整个消息 */
		memcpy(fc_buffer, &fc_conn->inBuffer[fc_conn->inCursor], fc_avail);
		/* 标记消息已消耗 */
		fc_conn->inStart = fc_conn->inCursor + fc_avail;
		/* 重置状态以备下次使用 */
		fc_conn->copy_already_done = 0;
		return fc_avail;
	}
	else
	{
		/* 我们必须返回部分消息 */
		memcpy(fc_buffer, &fc_conn->inBuffer[fc_conn->inCursor], fc_bufsize);
		/* 消息未从libpq的缓冲区中消耗 */
		fc_conn->copy_already_done += fc_bufsize;
		return fc_bufsize;
	}
}

/*
 * PQendcopy
 *
 * 请参见fe-exec.c以获取文档。
 */
int pqEndcopy3(PGconn *fc_conn)
{
	PGresult   *fc_result;

	if (fc_conn->asyncStatus != PGASYNC_COPY_IN &&
		fc_conn->asyncStatus != PGASYNC_COPY_OUT &&
		fc_conn->asyncStatus != PGASYNC_COPY_BOTH)
	{
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("no COPY in progress\n"));
		return 1;
	}

	/* 如果需要，发送CopyDone消息 */
	if (fc_conn->asyncStatus == PGASYNC_COPY_IN ||
		fc_conn->asyncStatus == PGASYNC_COPY_BOTH)
	{
		if (pqPutMsgStart('c', fc_conn) < 0 ||
			pqPutMsgEnd(fc_conn) < 0)
			return 1;

		/*
		 * 如果我们以扩展查询模式发送了COPY命令，则我们也必须发出
		 * Sync。
		 */
		if (fc_conn->cmd_queue_head &&
			fc_conn->cmd_queue_head->queryclass != PGQUERY_SIMPLE)
		{
			if (pqPutMsgStart('S', fc_conn) < 0 ||
				pqPutMsgEnd(fc_conn) < 0)
				return 1;
		}
	}

	/*
	 * 确保没有数据等待发送，如果我们是非阻塞的
	 * 并且刷新失败则中止
	 */
	if (pqFlush(fc_conn) && pqIsnonblocking(fc_conn))
		return 1;

	/* 返回到主动状态 */
	fc_conn->asyncStatus = PGASYNC_BUSY;

	/*
	 * 非阻塞连接可能在此时必须中止。如果每个人
	 * 都遵守规则则应该没有问题，但在错误场景中，预期的消息可能尚未到达。
	 * （我们假设后端的数据包化将确保CommandComplete
	 * 与CopyDone一起到达；是否存在不会发生的极端情况？）
	 */
	if (pqIsnonblocking(fc_conn) && PQisBusy(fc_conn))
		return 1;

	/* 等待完成响应 */
	fc_result = PQgetResult(fc_conn);

	/* 期望成功的结果 */
	if (fc_result && fc_result->resultStatus == PGRES_COMMAND_OK)
	{
		PQclear(fc_result);
		return 0;
	}

	/*
	 * 问题。出于向后兼容的原因，我们将错误消息作为通知发布
	 * （如果能摆脱这种愚蠢的做法就好了，但太多应用程序可能无法合理处理
	 * PQendcopy 的错误）。请注意，应用程序仍然可以从 PGconn 对象中获取错误
	 * 状态。
	 */
	if (fc_conn->errorMessage.len > 0)
	{
		/* 我们必须去掉末尾的换行符... 真烦人... */
		char		fc_svLast = fc_conn->errorMessage.data[fc_conn->errorMessage.len - 1];

		if (fc_svLast == '\n')
			fc_conn->errorMessage.data[fc_conn->errorMessage.len - 1] = '\0';
		pqInternalNotice(&fc_conn->noticeHooks, "%s", fc_conn->errorMessage.data);
		fc_conn->errorMessage.data[fc_conn->errorMessage.len - 1] = fc_svLast;
	}

	PQclear(fc_result);

	return 1;
}


/*
 * PQfn - 向 POSTGRES 后端发送函数调用。
 *
 * 请参阅 fe-exec.c 以获取文档。
 */
PGresult * pqFunctionCall3(PGconn *fc_conn, Oid fc_fnid,
				int *fc_result_buf, int *fc_actual_result_len,
				int fc_result_is_int,
				const PQArgBlock *fc_args, int fc_nargs)
{
	bool		fc_needInput = false;
	ExecStatusType fc_status = PGRES_FATAL_ERROR;
	char		fc_id;
	int			fc_msgLength;
	int			fc_avail;
	int			fc_i;

	/* 已通过 PQfn 验证 */
	Assert(fc_conn->pipelineStatus == PQ_PIPELINE_OFF);

	/* PQfn 已验证连接状态 */

	if (pqPutMsgStart('F', fc_conn) < 0 || /* 函数调用消息 */
		pqPutInt(fc_fnid, 4, fc_conn) < 0 ||	/* 函数 ID */
		pqPutInt(1, 2, fc_conn) < 0 || /* 格式代码数量 */
		pqPutInt(1, 2, fc_conn) < 0 || /* 格式代码：二进制 */
		pqPutInt(fc_nargs, 2, fc_conn) < 0)	/* 参数数量 */
	{
		/* 错误消息应该已经设置 */
		return NULL;
	}

	for (fc_i = 0; fc_i < fc_nargs; ++fc_i)
	{							/* len.int4 + 内容	   */
		if (pqPutInt(fc_args[fc_i].len, 4, fc_conn))
			return NULL;
		if (fc_args[fc_i].len == -1)
			continue;			/* 它是 NULL */

		if (fc_args[fc_i].isint)
		{
			if (pqPutInt(fc_args[fc_i].u.integer, fc_args[fc_i].len, fc_conn))
				return NULL;
		}
		else
		{
			if (pqPutnchar((char *) fc_args[fc_i].u.ptr, fc_args[fc_i].len, fc_conn))
				return NULL;
		}
	}

	if (pqPutInt(1, 2, fc_conn) < 0)	/* 结果格式代码：二进制 */
		return NULL;

	if (pqPutMsgEnd(fc_conn) < 0 ||
		pqFlush(fc_conn))
		return NULL;

	for (;;)
	{
		if (fc_needInput)
		{
			/* 等待数据到达（或通道关闭） */
			if (pqWait(true, false, fc_conn) ||
				pqReadData(fc_conn) < 0)
				break;
		}

		/*
		 * 扫描消息。如果数据用完了，循环尝试再次读取。
		 */
		fc_needInput = true;

		fc_conn->inCursor = fc_conn->inStart;
		if (pqGetc(&fc_id, fc_conn))
			continue;
		if (pqGetInt(&fc_msgLength, 4, fc_conn))
			continue;

		/*
		 * 尝试在此处验证消息类型/长度。长度小于4肯定是有问题的。只有对于少数
		 * 消息类型，大长度才应被相信。
		 */
		if (fc_msgLength < 4)
		{
			fc_handleSyncLoss(fc_conn, fc_id, fc_msgLength);
			break;
		}
		if (fc_msgLength > 30000 && !VALID_LONG_MESSAGE_TYPE(fc_id))
		{
			fc_handleSyncLoss(fc_conn, fc_id, fc_msgLength);
			break;
		}

		/*
		 * 如果消息主体尚未全部到位，则无法处理。
		 */
		fc_msgLength -= 4;
		fc_avail = fc_conn->inEnd - fc_conn->inCursor;
		if (fc_avail < fc_msgLength)
		{
			/*
			 * 在循环之前，如果需要，增加输入缓冲区，以容纳
			 * 整个消息。请参阅 parseInput 中的说明。
			 */
			if (pqCheckInBufferSpace(fc_conn->inCursor + (size_t) fc_msgLength,
									 fc_conn))
			{
				/*
				 * XXX 添加一些更好的恢复代码...计划是跳过
				 * 消息，使用其长度，然后报告错误。此时，只需将其视为同步丢失
				 * （这确实可能是！）
				 */
				fc_handleSyncLoss(fc_conn, fc_id, fc_msgLength);
				break;
			}
			continue;
		}

		/*
		 * 我们应该看到对命令的 V 或 E 响应，但可能先会收到 N
		 * 和/或 A 通知。我们还需要在返回之前吞下最后的 Z。
		 */
		switch (fc_id)
		{
			case 'V':			/* 函数结果 */
				if (pqGetInt(fc_actual_result_len, 4, fc_conn))
					continue;
				if (*fc_actual_result_len != -1)
				{
					if (fc_result_is_int)
					{
						if (pqGetInt(fc_result_buf, *fc_actual_result_len, fc_conn))
							continue;
					}
					else
					{
						if (pqGetnchar((char *) fc_result_buf,
									   *fc_actual_result_len,
									   fc_conn))
							continue;
					}
				}
				/* 正确完成的函数结果消息 */
				fc_status = PGRES_COMMAND_OK;
				break;
			case 'E':			/* 错误返回 */
				if (pqGetErrorNotice3(fc_conn, true))
					continue;
				fc_status = PGRES_FATAL_ERROR;
				break;
			case 'A':			/* 通知消息 */
				/* 处理通知并返回处理返回值 */
				if (fc_getNotify(fc_conn))
					continue;
				break;
			case 'N':			/* 通知 */
				/* 处理通知并返回处理返回值 */
				if (pqGetErrorNotice3(fc_conn, false))
					continue;
				break;
			case 'Z':			/* 后端准备好接受新查询 */
				if (fc_getReadyForQuery(fc_conn))
					continue;
				/* 消耗消息并退出 */
				fc_conn->inStart += 5 + fc_msgLength;

				/*
				 * 如果我们已经有结果对象（可能是错误），则使用
				 * 该对象。否则，如果我们看到函数结果消息，
				 * 报告 COMMAND_OK。否则，后端违反了
				 * 协议，所以要抱怨。
				 */
				if (!pgHavePendingResult(fc_conn))
				{
					if (fc_status == PGRES_COMMAND_OK)
					{
						fc_conn->result = PQmakeEmptyPGresult(fc_conn, fc_status);
						if (!fc_conn->result)
						{
							appendPQExpBufferStr(&fc_conn->errorMessage,
												 libpq_gettext("out of memory\n"));
							pqSaveErrorResult(fc_conn);
						}
					}
					else
					{
						appendPQExpBufferStr(&fc_conn->errorMessage,
											 libpq_gettext("protocol error: no function result\n"));
						pqSaveErrorResult(fc_conn);
					}
				}
				return pqPrepareAsyncResult(fc_conn);
			case 'S':			/* 参数状态 */
				if (fc_getParameterStatus(fc_conn))
					continue;
				break;
			default:
				/* 后端违反了协议。 */
				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("protocol error: id=0x%x\n"),
								  fc_id);
				pqSaveErrorResult(fc_conn);
				/* 信任指定的消息长度作为跳过的依据 */
				fc_conn->inStart += 5 + fc_msgLength;
				return pqPrepareAsyncResult(fc_conn);
		}

		/* 跟踪服务器到客户端的消息 */
		if (fc_conn->Pfdebug)
			pqTraceOutputMessage(fc_conn, fc_conn->inBuffer + fc_conn->inStart, false);

		/* 完成此消息，继续进行 */
		/* 信任指定的消息长度作为跳过的依据 */
		fc_conn->inStart += 5 + fc_msgLength;
		fc_needInput = false;
	}

	/*
	 * 我们只有在无法读取数据时才会退出循环。
	 * conn->errorMessage 已通过 pqWait 或 pqReadData 设置。我们希望
	 * 将其附加到任何已接收的错误消息。
	 */
	pqSaveErrorResult(fc_conn);
	return pqPrepareAsyncResult(fc_conn);
}


/*
 * 构造启动数据包
 *
 * 返回一个 malloc 分配的数据包缓冲区，如果内存不足则返回 NULL
 */
char * pqBuildStartupPacket3(PGconn *fc_conn, int *fc_packetlen,
					  const PQEnvironmentOption *fc_options)
{
	char	   *fc_startpacket;

	*fc_packetlen = fc_build_startup_packet(fc_conn, NULL, fc_options);
	fc_startpacket = (char *) malloc(*fc_packetlen);
	if (!fc_startpacket)
		return NULL;
	*fc_packetlen = fc_build_startup_packet(fc_conn, fc_startpacket, fc_options);
	return fc_startpacket;
}

/*
 * 构建一个启动数据包，给定一个填充好的 PGconn 结构。
 *
 * 我们需要确定需要多少空间，然后填充它。
 * 为了避免重复逻辑，这个例程被调用两次：第一次
 * （当 packet == NULL 时）只是计算所需的空间，第二次
 * （当 packet == 已分配空间时）则填充它。返回值是使用的字节数。
 */
static int fc_build_startup_packet(const PGconn *fc_conn, char *fc_packet,
					 const PQEnvironmentOption *fc_options)
{
	int			fc_packet_len = 0;
	const PQEnvironmentOption *fc_next_eo;
	const char *fc_val;

	/* 协议版本优先。 */
	if (fc_packet)
	{
		ProtocolVersion fc_pv = pg_hton32(fc_conn->pversion);

		memcpy(fc_packet + fc_packet_len, &fc_pv, sizeof(ProtocolVersion));
	}
	fc_packet_len += sizeof(ProtocolVersion);

	/* 添加用户名、数据库名、选项 */

#define ADD_STARTUP_OPTION(optname, optval) \
	do { \
		if (fc_packet) \
			strcpy(fc_packet + fc_packet_len, optname); \
		fc_packet_len += strlen(optname) + 1; \
		if (fc_packet) \
			strcpy(fc_packet + fc_packet_len, optval); \
		fc_packet_len += strlen(optval) + 1; \
	} while(0)

	if (fc_conn->pguser && fc_conn->pguser[0])
		ADD_STARTUP_OPTION("user", fc_conn->pguser);
	if (fc_conn->dbName && fc_conn->dbName[0])
		ADD_STARTUP_OPTION("database", fc_conn->dbName);
	if (fc_conn->replication && fc_conn->replication[0])
		ADD_STARTUP_OPTION("replication", fc_conn->replication);
	if (fc_conn->pgoptions && fc_conn->pgoptions[0])
		ADD_STARTUP_OPTION("options", fc_conn->pgoptions);
	if (fc_conn->send_appname)
	{
		/* 如存在则使用 appname，否则使用默认值 */
		fc_val = fc_conn->appname ? fc_conn->appname : fc_conn->fbappname;
		if (fc_val && fc_val[0])
			ADD_STARTUP_OPTION("application_name", fc_val);
	}

	if (fc_conn->client_encoding_initial && fc_conn->client_encoding_initial[0])
		ADD_STARTUP_OPTION("client_encoding", fc_conn->client_encoding_initial);

	/* 添加任何环境驱动的 GUC 设置 */
	for (fc_next_eo = fc_options; fc_next_eo->envName; fc_next_eo++)
	{
		if ((fc_val = getenv(fc_next_eo->envName)) != NULL)
		{
			if (pg_strcasecmp(fc_val, "default") != 0)
				ADD_STARTUP_OPTION(fc_next_eo->pgName, fc_val);
		}
	}

	/* 添加尾随终止符 */
	if (fc_packet)
		fc_packet[fc_packet_len] = '\0';
	fc_packet_len++;

	return fc_packet_len;
}
